﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Interop;
using System.Windows.Threading;

using Perseus;

namespace Hetymine.Executioner.Plugins.General.Notes {
    /// <summary>
    /// Interaction logic for Display.xaml
    /// </summary>
    public partial class Display : Window {
        #region Private Fields
        private DispatcherTimer _Timer;        
        private int _Index = -1;
        private static Dictionary<int, Rect> _DisplayRects;
        private bool _NoLocationUpdate = false;
        private bool _Collapsed = false;
        private double _DefaultFontSize;
        private double _MaxFontSize;
        #endregion

        #region Initialization
        public Display() : this(string.Empty, 0) { }
        public Display(string data, int time) : this(data, time, true) { }
        public Display(string data, int time, bool autoPosition) {
            InitializeComponent();

            this.ResizeMode = ResizeMode.NoResize;

            this.cNote.AcceptsReturn = true;            
            this.cNote.AcceptsTab = true;
            this.cNote.MouseWheel += new MouseWheelEventHandler(Note_MouseWheel);
            this.cNote.KeyDown += new KeyEventHandler(Note_KeyDown);
            // Prevent stupid input
            this._DefaultFontSize = Math.Max(7, Executioner.Config.GetDouble("Notes", "Default Font Size", 12));
            this._MaxFontSize = Math.Max(this._DefaultFontSize, Executioner.Config.GetDouble("Notes", "Max Font Size", 72));
            this.cNote.FontSize = this._DefaultFontSize;

            this.cCollapsed.Visibility = Visibility.Collapsed;
            this.cCollapsed.MouseUp += new MouseButtonEventHandler(Collapsed_MouseUp);

            this.Text = data;
            this.Time = time;
            this.AutoPosition = autoPosition;
            
            this._Timer = new DispatcherTimer();
            this._Timer.Tick += new EventHandler(Timer_Tick);

            this.Loaded += new RoutedEventHandler(Display_Loaded);
            this.LocationChanged += new EventHandler(Display_LocationChanged);
            this.Closing += new CancelEventHandler(Display_Closing);            
            this.KeyUp += new KeyEventHandler(Display_KeyUp);
        }

        private void Note_KeyDown(object sender, KeyEventArgs e) {
            if (e.Key == Key.NumPad0 && Keyboard.Modifiers == (Keyboard.Modifiers | ModifierKeys.Control)) {
                this.cNote.FontSize = this._DefaultFontSize;
                e.Handled = true;
            }
        }
        private void Display_Loaded(object sender, RoutedEventArgs e) {
            this.InitializeSystemMenuItems();
            //this.SizeToContent = System.Windows.SizeToContent.Manual;
        }
        static Display() {
            Display._DisplayRects = new Dictionary<int, Rect>();
        }
        #endregion

        #region Events
        private void Note_MouseWheel(object sender, MouseWheelEventArgs e) {
            if (e.Delta > 0) {
                if (this.cNote.FontSize < this._MaxFontSize) {
                    this.cNote.FontSize = Math.Min(this._MaxFontSize, this.cNote.FontSize + 1);
                }
            }
            else if (e.Delta < 0 && this.cNote.FontSize > 6) {
                this.cNote.FontSize = Math.Max(6, this.cNote.FontSize - 1);
            }
        }
        private void Collapsed_MouseUp(object sender, MouseButtonEventArgs e) {
            this.Collapsed = false;
        }
        private void Display_KeyUp(object sender, KeyEventArgs e) {
            if (e.Key == Key.Escape) {
                if (!this.Collapsed) {
                    this.Collapsed = true;
                }
                else {
                    this.Close();
                }
                e.Handled = true;
            }
            else if (e.Key == Key.Space && this.Collapsed) {
                this.Collapsed = false;
                e.Handled = true;
            }
        }
        private void Display_LocationChanged(object sender, EventArgs e) {
            if (this.AutoPosition && !this._NoLocationUpdate) {
                this._Timer.IsEnabled = false;
                this.AutoPosition = false;
                Display._DisplayRects.Remove(this._Index);                
            }
        }
        private void Display_Closing(object sender, CancelEventArgs e) {
            if (this._Timer.IsEnabled) {
                this._Timer.IsEnabled = false;
                if (this.AutoPosition && Display._DisplayRects.ContainsKey(this._Index)) {
                    Display._DisplayRects.Remove(this._Index);
                }
            }            
        }        
        #endregion

        #region Context Menu Insert
        [DllImport("user32.dll")]
        private static extern IntPtr GetSystemMenu(IntPtr hWnd, bool bRevert);
        [DllImport("user32.dll")]
        private static extern bool InsertMenu(IntPtr hMenu, uint uPosition, uint uFlags, uint uIDNewItem, string lpNewItem);
        [DllImport("user32.dll")]
        private static extern uint CheckMenuItem(IntPtr hMenu, uint uIDCheckItem, uint uCheck);

        /// Define our Constants we will use
        public const uint WM_SYSCOMMAND = 0x112;
        public const uint MF_SEPARATOR = 0x800;
        public const uint MF_BYPOSITION = 0x400;
        public const uint MF_STRING = 0x0;
        public const uint MF_CHECKED = 0x8;
        public const uint MF_UNCHECKED = 0x0;

        public const int _TopmostMenuId = 1000;
        public const int _ShowInTaskBarMenuId = 1001;

        private IntPtr _SystemMenuHandle = IntPtr.Zero;

        private void InitializeSystemMenuItems() {
            IntPtr windowHandle = new WindowInteropHelper(this).Handle;

            // Get the Handle for the system menu
            this._SystemMenuHandle = GetSystemMenu(windowHandle, false);
            
            uint topMostFlags = MF_UNCHECKED;
            if (this.Topmost) {
                topMostFlags = MF_CHECKED;
            }

            uint showInTaskBarFlags = MF_UNCHECKED;
            if (this.ShowInTaskbar) {
                showInTaskBarFlags = MF_CHECKED;
            }

            // Insert new menu items just before the "Close" menu item
            Display.InsertMenu(this._SystemMenuHandle, 5, MF_BYPOSITION | MF_SEPARATOR, 0, string.Empty);
            Display.InsertMenu(this._SystemMenuHandle, 6, MF_BYPOSITION | MF_STRING | topMostFlags, Display._TopmostMenuId, "On Top");
            Display.InsertMenu(this._SystemMenuHandle, 7, MF_BYPOSITION | MF_STRING | showInTaskBarFlags, Display._ShowInTaskBarMenuId, "Show In Taskbar");
            
            // Attach our WndProc handler to this Window
            HwndSource source = HwndSource.FromHwnd(windowHandle);
            source.AddHook(new HwndSourceHook(this.WndProc));
        }
        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
            // Check if a System Command has been executed
            if (msg == WM_SYSCOMMAND) {
                // Execute the appropriate code for the System Menu item that was clicked
                switch (wParam.ToInt32()) {
                    case Display._TopmostMenuId:
                        this.Topmost = !this.Topmost;
                        handled = true;
                        break;
                    case Display._ShowInTaskBarMenuId:
                        this.ShowInTaskbar = !this.ShowInTaskbar;
                        handled = true;
                        break;
                }
            }

            return IntPtr.Zero;
        }
        #endregion

        public string Text {
            get { return cNote.Text; }
            set { cNote.Text = value; }
        }
        public int Time { get; set; }
        public bool AutoPosition { get; set; }
        public new bool Topmost {
            get { return base.Topmost; }
            set {
                base.Topmost = value;
                if (this._SystemMenuHandle != IntPtr.Zero) {
                    if (value) {
                        Display.CheckMenuItem(this._SystemMenuHandle, Display._TopmostMenuId, MF_CHECKED);
                    }
                    else {
                        Display.CheckMenuItem(this._SystemMenuHandle, Display._TopmostMenuId, MF_UNCHECKED);
                    }
                }
            }
        }
        public new bool ShowInTaskbar {
            get { return base.ShowInTaskbar; }
            set {
                base.ShowInTaskbar = value;
                if (this._SystemMenuHandle != IntPtr.Zero) {
                    if (value) {
                        Display.CheckMenuItem(this._SystemMenuHandle, Display._ShowInTaskBarMenuId, MF_CHECKED);
                    }
                    else {
                        Display.CheckMenuItem(this._SystemMenuHandle, Display._ShowInTaskBarMenuId, MF_UNCHECKED);
                    }
                }
            }
        }
        public bool Collapsed {
            get { return this._Collapsed; }
            set {
                if (value) {
                    this._Collapsed = true;
                    this.cNote.Visibility = Visibility.Collapsed;
                    this.cCollapsed.Visibility = Visibility.Visible;
                }
                else {
                    this._Collapsed = false;
                    this.cCollapsed.Visibility = Visibility.Collapsed;
                    this.cNote.Visibility = Visibility.Visible;
                    this.cNote.Focus();
                }
            }
        }

        public new void Show() {
            this._Timer.IsEnabled = false;

            if (this.Time > 0) {
                if (this.Text.IsEmpty()) {
                    Executioner.Sounds.ErrorNotify();
                    return;
                }

                this.ContextMenu = null;
                
                this._Timer.Interval = TimeSpan.FromMilliseconds(Math.Max(100, this.Time));
                this._Timer.IsEnabled = true;

                base.Show();

                if (this.AutoPosition) {
                    this._NoLocationUpdate = true;

                    int display = Executioner.Config.GetInt("Notes.Timeout", "Display", -1);
                    string alignment = Executioner.Config.GetString("Notes.Timeout", "Alignment", "centre centre");
                    double left = Executioner.Config.GetInt("Notes.Timeout", "Left", 0);
                    double top = Executioner.Config.GetInt("Notes.Timeout", "Top", 0);

                    int margin = Executioner.Config.GetInt("Notes.Timeout", "Margin", 20);

                    Point p = Perseus.Windows.Display.Position(display, alignment, left, top, this.ActualWidth, this.ActualHeight);

                    int index = -1;

                    if (Display._DisplayRects.Count > 0) {
                        // Calculate the total area of all currently displayed 
                        // autopositioned timer windows
                        Rect total = new Rect(p.X, p.Y, this.ActualWidth, this.ActualHeight);
                        foreach (int key in Display._DisplayRects.Keys) {
                            if (key > index) {
                                index = key;
                            }

                            Rect r = Display._DisplayRects[key];
                      
                            if (r.X < total.X) {
                                total.Width += total.X - r.X;
                                total.X = r.X;
                            }
                            if (r.X + r.Width > total.X + total.Width) {
                                total.Width = r.X + r.Width - total.X;
                            }

                            if (r.Y < total.Y) {
                                total.Height += total.Y - r.Y;
                                total.Y = r.Y;
                            }
                            if (r.Y + r.Height > total.Y + total.Height) {
                                total.Height = r.Y + r.Height - total.Y;
                            }
                        }

                        // Check if display interects with area of other notes and if so reposition
                        // while ensuring it doesn't go off screen.

                        // TODO: More advanced intersect placement so it's not jsut a rectangle.
                        var area = Perseus.Windows.Display.FromIndex(display).WorkingArea;
                        if (total.IntersectsWith(new Rect(p.X, p.Y, this.ActualWidth, this.ActualHeight))) {
                            bool positionChanged = false;
                            
                            // Check bottom position will fit
                            double pos = total.Bottom + margin + this.ActualHeight;
                            if (pos < area.Bottom) {
                                p.Y = total.Bottom + margin;
                                positionChanged = true;
                            }

                            // Check top position
                            if (!positionChanged) {
                                pos = total.Top - margin - this.ActualHeight;
                                if (pos > area.Top) {
                                    p.Y = pos;
                                    positionChanged = true;
                                }
                            }

                            // Check right position
                            if (!positionChanged) {
                                pos = total.Right + margin + this.ActualWidth;
                                if (pos < area.Right) {
                                    p.X = total.Right + margin;
                                    positionChanged = true;
                                }
                            }

                            // Check left position
                            if (!positionChanged) {
                                pos = total.Left - margin - this.ActualWidth;
                                if (pos > area.Left) {
                                    p.X = pos;
                                    positionChanged = true;
                                }
                            }

                            // If it still doesn't fit, reset
                            if (!positionChanged) {
                                Display._DisplayRects.Clear();
                            }
                        }
                    }

                    this.Left = p.X;
                    this.Top = p.Y;

                    this._Index = index + 1;
                    Display._DisplayRects[this._Index] = new Rect(this.Left, this.Top, this.ActualWidth, this.ActualHeight);

                    this._NoLocationUpdate = false;
                }
            }
            else {                
                base.Show();

                if (this.AutoPosition) {
                    int display = Executioner.Config.GetInt("Notes.Static", "Display", -1);
                    string alignment = Executioner.Config.GetString("Notes.Static", "Alignment", "centre centre");
                    double left = Executioner.Config.GetInt("Notes.Static", "Left", 0);
                    double top = Executioner.Config.GetInt("Notes.Static", "Top", 0);

                    Point p = Perseus.Windows.Display.Position(display, alignment, left, top, this.ActualWidth, this.ActualHeight);

                    this.Left = p.X;
                    this.Top = p.Y;
                }
            }

            this.Activate();
        }

        private void Timer_Tick(object sender, EventArgs e) {
            this._Timer.IsEnabled = false;
            if (this.AutoPosition && Display._DisplayRects.ContainsKey(this._Index)) {
                Display._DisplayRects.Remove(this._Index);
            }
            this.Close();
        }
    }
}
